/**
 * 
 */
package com.maohi.software.arii.utils;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBException;

import org.apache.log4j.Logger;

import com.google.inject.Module;
import com.google.inject.Provider;
import com.maohi.software.arii.Arii;
import com.maohi.software.arii.events.DependencyListener;
import com.maohi.software.arii.exceptions.DuplicateIdException;
import com.maohi.software.arii.exceptions.MissingIdException;
import com.maohi.software.arii.interfaces.Activator;
import com.maohi.software.arii.jaxb2.Plugin;
import com.maohi.software.arii.jaxb2.Plugin.Jaxb.Xml;
import com.maohi.software.arii.jaxb2.Plugin.Jaxb.Xsd;

/**
 * @author heifara
 * 
 */
public class Providers {

	private static class ListActivator implements Provider<List<Activator>> {
		private static Logger logger = Logger.getLogger(Providers.ListDependencyListener.class.getName());

		@Override
		public List<Activator> get() {
			try {

				Map<String, Plugin> iPlugins = Arii.getInstance(Keys.getMapStringPlugin());
				ClassLoader iClassLoader = Arii.getInstance(ClassLoader.class);

				List<Activator> iResults = new ArrayList<Activator>();
				Iterator<Plugin> iIterator = iPlugins.values().iterator();
				while (iIterator.hasNext()) {
					Plugin iPlugin = iIterator.next();

					if (iPlugin.getActivator() != null && !iPlugin.getActivator().isEmpty()) {
						Class<?> iClass = Class.forName(iPlugin.getActivator(), true, iClassLoader);

						Object iObject = iClass.newInstance();
						if (!(iObject instanceof Activator)) {
							throw new ClassCastException(String.format("%s does not implement %s", iObject, Activator.class));
						}

						iResults.add((Activator) iObject);
					}

				}
				return iResults;
			} catch (Exception aException) {
				logger.error(aException.getMessage(), aException);
				return null;
			}
		}
	}

	private static class ListDependencyListener implements Provider<List<DependencyListener>> {
		private static Logger logger = Logger.getLogger(Providers.ListDependencyListener.class.getName());

		@Override
		public List<DependencyListener> get() {
			List<DependencyListener> iResults = new ArrayList<DependencyListener>();
			logger.debug(String.format("size(%d)", iResults.size()));
			return iResults;
		}
	}

	private static class ListJaxB implements Provider<List<?>> {
		private static Logger logger = Logger.getLogger(Providers.ListJaxB.class.getName());
		private Xsd xsd;
		private List<Xml> xmls;
		private ClassLoader classloader;

		public ListJaxB(Xsd aXsd, List<Xml> iXmls, ClassLoader aClassLoader) {
			this.xsd = aXsd;
			this.xmls = iXmls;
			this.classloader = aClassLoader;
		}

		@Override
		public List<?> get() {
			List<Object> iResults = new ArrayList<>();
			try {
				Class<?> iClass = Class.forName(xsd.getValue(), false, classloader);
				for (Xml iXml : this.xmls) {
					for (String iFilename : iXml.getFilename()) {
						URL iUrl = classloader.getResource(iFilename);

						try (InputStream iInputStream = iUrl.openStream()) {
							Object iElement = Arii.getInstance(iClass, iInputStream);
							iResults.add(iElement);
						} catch (JAXBException | IOException aException) {
							logger.error(aException.getMessage(), aException);
						}
					}
				}

			} catch (ClassNotFoundException aException) {
				logger.error(aException.getMessage(), aException);
			}
			return iResults;

		}
	}

	private static class ListModule implements Provider<List<Module>> {
		private static Logger logger = Logger.getLogger(Providers.ListModule.class.getName());

		private Module parent;
		private boolean override;

		public ListModule(Module aModule, boolean aOverride) {
			this.parent = aModule;
			this.override = aOverride;
		}

		@Override
		public List<Module> get() {
			try {

				Map<String, Plugin> iPlugins = Arii.getInstance(Keys.getMapStringPlugin());
				ClassLoader iClassLoader = Arii.getInstance(ClassLoader.class);

				if (this.override) {
					return this.getOverride(iClassLoader, iPlugins);
				} else {
					return this.get(iClassLoader, iPlugins);
				}

			} catch (Exception aException) {
				logger.error(aException.getMessage(), aException);
				return new ArrayList<>();
			}
		}

		private List<Module> get(ClassLoader aClassLoader, Map<String, Plugin> iPlugins) throws ClassNotFoundException {
			List<Module> iResults = new ArrayList<Module>();
			iResults.add(this.parent);

			Iterator<Plugin> iIterator = iPlugins.values().iterator();
			while (iIterator.hasNext()) {
				Plugin iPlugin = iIterator.next();
				if (iPlugin.getModules() != null) {
					for (com.maohi.software.arii.jaxb2.Plugin.Modules.Module iModule : iPlugin.getModules().getModule()) {
						if (iModule.getOverride() != null) {
							continue;
						}

						Object iObject = Arii.getInstance(iModule.getValue(), aClassLoader);
						if (!(iObject instanceof Module)) {
							throw new ClassCastException(String.format("%s does not implement %s", iObject, Module.class));
						}

						iResults.add((Module) iObject);
					}
				}
			}
			return iResults;
		}

		private List<Module> getOverride(ClassLoader aClassLoader, Map<String, Plugin> iPlugins) throws ClassNotFoundException {
			List<Module> iResults = new ArrayList<Module>();

			Iterator<Plugin> iIterator = iPlugins.values().iterator();
			while (iIterator.hasNext()) {
				Plugin iPlugin = iIterator.next();
				if (iPlugin.getModules() != null) {
					for (com.maohi.software.arii.jaxb2.Plugin.Modules.Module iModule : iPlugin.getModules().getModule()) {
						if (iModule.getOverride() == null) {
							continue;
						}

						Object iObject = Arii.getInstance(iModule.getValue(), aClassLoader);
						if (!(iObject instanceof Module)) {
							throw new ClassCastException(String.format("%s does not implement %s", iObject, Module.class));
						}

						iResults.add((Module) iObject);
					}
				}
			}
			return iResults;
		}
	}

	private static class MapModuleModule implements Provider<Map<Module, Module>> {
		private static Logger logger = Logger.getLogger(Providers.MapModuleModule.class.getName());

		@Override
		public Map<Module, Module> get() {
			Map<Module, Module> iResults = new LinkedHashMap<>();
			try {

				Map<String, Plugin> iPlugins = Arii.getInstance(Keys.getMapStringPlugin());
				ClassLoader iClassLoader = Arii.getInstance(ClassLoader.class);

				Iterator<Plugin> iIterator = iPlugins.values().iterator();
				while (iIterator.hasNext()) {
					Plugin iPlugin = iIterator.next();
					if (iPlugin.getModules() != null) {
						for (com.maohi.software.arii.jaxb2.Plugin.Modules.Module iModule : iPlugin.getModules().getModule()) {
							if (iModule.getOverride() == null) {
								continue;
							}

							Object iOverridedModule = Arii.getInstance(iModule.getOverride(), iClassLoader);
							if (!(iOverridedModule instanceof Module)) {
								throw new ClassCastException(String.format("%s does not implement %s", iOverridedModule, Module.class));
							}

							Object iModuleOverride = Arii.getInstance(iModule.getValue(), iClassLoader);
							if (!(iModuleOverride instanceof Module)) {
								throw new ClassCastException(String.format("%s does not implement %s", iModuleOverride, Module.class));
							}

							iResults.put((Module) iOverridedModule, (Module) iModuleOverride);
						}
					}
				}

			} catch (Exception aException) {
				logger.error(aException.getMessage(), aException);
			}
			return iResults;
		}
	}

	private static class MapStringPlugin implements Provider<Map<String, Plugin>> {
		private static Logger logger = Logger.getLogger(Providers.MapStringPlugin.class.getName());

		@Override
		public Map<String, Plugin> get() {
			try {
				Map<String, Plugin> iResult = new LinkedHashMap<String, Plugin>();
				Enumeration<URL> iUrls = Thread.currentThread().getContextClassLoader().getResources("plugin.xml");
				while (iUrls.hasMoreElements()) {
					InputStream iInputStream = iUrls.nextElement().openStream();

					Plugin iPlugin = Arii.getInstance(Plugin.class, iInputStream);
					iResult.put(Plugins.getKey(iPlugin), iPlugin);

					iInputStream.close();
				}
				return iResult;
			} catch (Exception aException) {
				logger.error(aException.getMessage(), aException);
				return null;
			}
		}
	}

	private static class MapXsdListXml implements Provider<Map<Xsd, List<Xml>>> {
		private static Logger logger = Logger.getLogger(Providers.MapXsdListXml.class.getName());

		@Override
		public Map<Xsd, List<Xml>> get() {
			Map<String, Xsd> iKeys = new HashMap<>();
			Map<Xsd, List<Xml>> iResult = new HashMap<Xsd, List<Xml>>();
			try {

				Map<String, Plugin> iPlugins = Arii.getInstance(Keys.getMapStringPlugin());

				for (Plugin iPlugin : iPlugins.values()) {
					if (iPlugin.getJaxb() != null) {
						for (Xsd iXsd : iPlugin.getJaxb().getXsd()) {
							if (!iKeys.containsKey(iXsd.getId())) {
								iResult.put(iXsd, new ArrayList<Xml>());
								iKeys.put(iXsd.getId(), iXsd);
							} else {
								throw new DuplicateIdException(String.format("Id[%s] already exits: Xsd[%s]", iXsd.getId(), iXsd.getFilename()));
							}
						}
					}
				}

				for (Plugin iPlugin : iPlugins.values()) {
					if (iPlugin.getJaxb() != null) {
						for (Xml iXml : iPlugin.getJaxb().getXml()) {
							Xsd iXsd = iKeys.get(iXml.getId());
							List<Xml> iXmls = iResult.get(iXsd);
							if (iXmls != null) {
								iXmls.add(iXml);
							} else {
								throw new MissingIdException(String.format("Id[%s] is missing", iXml.getId()));
							}
						}
					}
				}
			} catch (Exception aException) {
				logger.error(aException.getMessage(), aException);
			}
			return iResult;
		}
	}

	public static Provider<? extends List<Activator>> getListActivator() {
		return new ListActivator();
	}

	public static Provider<? extends List<DependencyListener>> getListDependencyListener() {
		return new ListDependencyListener();
	}

	public static Provider<? extends List<?>> getListJaxB(Xsd aXsd, List<Xml> iXmls, ClassLoader aClassLoader) {
		return new ListJaxB(aXsd, iXmls, aClassLoader);
	}

	public static Provider<? extends List<Module>> getListModule(Module aModule, boolean aOverride) {
		return new ListModule(aModule, aOverride);
	}

	public static Provider<? extends Map<Module, Module>> getMapModuleModule() {
		return new MapModuleModule();
	}

	public static Provider<? extends Map<String, Plugin>> getMapStringPlugin() {
		return new MapStringPlugin();
	}

	public static Provider<? extends Map<Xsd, List<Xml>>> getMapXsdListXml() {
		return new MapXsdListXml();
	}

}
